package io.kiki.sba.registry.client.provider;

import io.kiki.sba.registry.client.api.ClientConfig;
import io.kiki.sba.registry.client.api.Register;
import io.kiki.sba.registry.client.api.registration.BaseRegistration;
import io.kiki.sba.registry.client.auth.AuthManager;
import io.kiki.sba.registry.client.constants.ValueConstants;
import io.kiki.sba.registry.client.constants.VersionConstants;
import io.kiki.sba.registry.client.util.StringUtils;
import io.kiki.sba.registry.core.model.BaseRegister;
import lombok.Getter;
import lombok.Setter;

import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

@Setter
@Getter
public abstract class AbstractInternalRegister implements Register {


    private final AtomicLong initialVersion = new AtomicLong(VersionConstants.UNINITIALIZED_VERSION);

    @Setter
    private AuthManager authManager;

    private volatile boolean registered = false;

    private volatile boolean enabled = true;

    private volatile boolean refused = false;

    private AtomicLong pubVersion = new AtomicLong(VersionConstants.UNINITIALIZED_VERSION);

    private AtomicLong ackVersion = new AtomicLong(VersionConstants.UNINITIALIZED_VERSION);

    private volatile long timestamp = System.currentTimeMillis();

    private volatile int registerCount = 0;

    private volatile String requestId = UUID.randomUUID().toString();

    private ReadWriteLock lock = new ReentrantReadWriteLock();


    protected Lock readLock = lock.readLock();

    protected Lock writeLock = lock.writeLock();


    public abstract Object assembly();


    @Override
    public boolean isRegistered() {
        readLock.lock();
        try {
            return registered;
        } finally {
            readLock.unlock();
        }
    }


    void waitToSync() {
        writeLock.lock();
        try {
            this.registered = false;
            this.requestId = UUID.randomUUID().toString();
        } finally {
            writeLock.unlock();
        }
    }


    public boolean syncOK(String requestId, long version, boolean refused) {
        writeLock.lock();
        try {
            if (this.requestId.equals(requestId)) {
                this.registered = true;
                this.refused = refused;
                this.setAckVersion(version);
                return true;
            }
            return false;
        } finally {
            writeLock.unlock();
        }
    }


    @Override
    public boolean isEnabled() {
        readLock.lock();
        try {
            return enabled;
        } finally {
            readLock.unlock();
        }
    }

    /**
     * Sets enabled.
     *
     * @param requestId the request id
     */
    public void refused(String requestId) {
        writeLock.lock();
        try {
            if (this.requestId.equals(requestId)) {
                this.enabled = false;
                this.refused = true;
            }
        } finally {
            writeLock.unlock();
        }
    }


    public boolean isDone() {
        readLock.lock();
        try {
            return (this.isRegistered() && this.pubVersion.get() == this.ackVersion.get()) || this.isRefused();
        } finally {
            readLock.unlock();
        }
    }

    public SyncTask assemblySyncTask() {
        readLock.lock();
        try {
            SyncTask syncTask = new SyncTask();
            syncTask.setRequestId(requestId);
            syncTask.setRequest(assembly());
            syncTask.setDone(isDone());
            return syncTask;
        } finally {
            readLock.unlock();
        }
    }


    public void setAckVersion(Long version) {
        if (null == version) {
            return;
        }

        long current = ackVersion.get();
        if (version <= current) {
            return;
        }

        boolean result = this.ackVersion.compareAndSet(current, version);
        if (result) {
            return;
        }

        setAckVersion(version);
    }


    @Override
    public void reset() {
        writeLock.lock();
        try {
            this.registered = false;
            this.registerCount = 0;
            this.timestamp = System.currentTimeMillis();
            this.ackVersion = new AtomicLong(initialVersion.longValue());
            this.requestId = UUID.randomUUID().toString();
        } finally {
            writeLock.unlock();
        }
    }


    @Override
    public void unregister() {
        writeLock.lock();
        try {
            this.enabled = false;
            this.pubVersion.incrementAndGet();
            this.requestId = UUID.randomUUID().toString();
            this.registerCount = 0;
        } finally {
            writeLock.unlock();
        }
    }


    boolean isRefused() {
        readLock.lock();
        try {
            return refused;
        } finally {
            readLock.unlock();
        }
    }


    @Override
    public long getTimestamp() {
        readLock.lock();
        try {
            return timestamp;
        } finally {
            readLock.unlock();
        }
    }


    void setTimestamp(long timestamp) {
        writeLock.lock();
        try {
            this.timestamp = timestamp;
        } finally {
            writeLock.unlock();
        }
    }


    void setAuthSignature(BaseRegister register) {
        // auth signature
        if (null != authManager) {
            Map<String, String> authAttributes = authManager.getAuthContent(register);

            // merge auth attributes with exists register attributes
            Map<String, String> registerAttributes = register.getAttributes();
            if (null == registerAttributes) {
                registerAttributes = new HashMap<String, String>();
            }
            registerAttributes.putAll(authAttributes);
            register.setAttributes(registerAttributes);
        }
    }


    protected void setAttributes(BaseRegistration from, BaseRegister to, ClientConfig clientConfig) {
        if (StringUtils.isNotEmpty(from.getInstanceId())) {
            to.setInstanceId(from.getInstanceId());
        } else {
            to.setInstanceId(clientConfig.getInstanceId());
        }
        if (StringUtils.isNotEmpty(clientConfig.getZone())) {
            to.setZone(clientConfig.getZone());
        } else {
            to.setZone(ValueConstants.DEFAULT_ZONE);
        }
        if (StringUtils.isNotEmpty(from.getAppName())) {
            to.setAppName(from.getAppName());
        } else {
            to.setAppName(clientConfig.getAppName());
        }
        if (StringUtils.isNotEmpty(from.getIp())) {
            to.setIp(from.getIp());
        } else {
            to.setIp(clientConfig.getIp());
        }
        to.setDataId(from.getDataId());
        to.setGroup(from.getGroup());
        to.setVersion(this.getPubVersion().get());
        to.setTimestamp(this.getTimestamp());
    }


    @Setter
    @Getter
    public static class SyncTask {

        private String requestId;

        private Object request;


        private boolean done;


    }
}
